import 'dart:io';
import 'package:code_assets/code_assets.dart';
import 'package:logging/logging.dart';
import 'package:hooks/hooks.dart';
import 'package:native_toolchain_c/native_toolchain_c.dart';
import 'package:native_toolchain_c/src/cbuilder/run_cbuilder.dart';
import 'package:native_toolchain_c/src/native_toolchain/android_ndk.dart';

const _dylibPrefix = {
  OS.android: 'lib',
  OS.fuchsia: 'lib',
  OS.iOS: 'lib',
  OS.linux: 'lib',
  OS.macOS: 'lib',
  OS.windows: '',
};

String stripPrefix(OS os, String name) {
  final prefix = _dylibPrefix[os];
  if (prefix == null) {
    throw UnsupportedError('OS $os does not have a library prefix');
  }
  if (name.startsWith(prefix)) {
    return name.substring(prefix.length);
  }
  return name;
}

const androidNdkArchABIMap = {
  Architecture.arm: 'arm-linux-androideabi',
  Architecture.arm64: 'aarch64-linux-android',
  Architecture.ia32: 'i686-linux-android',
  Architecture.x64: 'x86_64-linux-android',
  Architecture.riscv64: 'riscv64-linux-android',
};

void main(List<String> args) async {
  await build(args, (input, output) async {
    const String libcalcManagerPath = 'src/calculator/src/CalcManager';
    final OS targetOs = input.config.code.targetOS;
    final packageName = stripPrefix(targetOs, input.packageName);
    final Architecture targetArchitecture =
        input.config.code.targetArchitecture;

    List<String> flags = [];
    List<String> frameworks = [];
    List<String> libraries = [];

    var defines = <String, String?>{
    };

    var forcedIncludes = <String>[];
    // This is the crossplatform fix for the previous workaround
    // that required a define with quoatation marks around it
    // which breaks CL.exe.
    forcedIncludes.add('src/include/pch.h');
    // forcedIncludes.add('src/include/intsafe.h');
    // forcedIncludes.add('src/calculator/src/CalcManager/Header Files/CalcEngine.h');
    // forcedIncludes.add('src/calculator/src/CalcManager/Ratpack/ratpak.h');

    // Android
    if (targetOs == OS.android) {
      // Add flag for 16k pages.
      flags.add('-Wl,-z,max-page-size=16384');
    }

    final builder = CBuilder.library(
      name: packageName,
      assetName: 'native_liblsl.dart',
      pic: true,
      std: 'c++17',
      sources: [
        '$libcalcManagerPath/CEngine/calc.cpp',
        '$libcalcManagerPath/CEngine/CalcInput.cpp',
        '$libcalcManagerPath/CEngine/CalcUtils.cpp',
        '$libcalcManagerPath/CEngine/History.cpp',
        '$libcalcManagerPath/CEngine/Number.cpp',
        '$libcalcManagerPath/CEngine/Rational.cpp',
        '$libcalcManagerPath/CEngine/RationalMath.cpp',
        '$libcalcManagerPath/CEngine/scicomm.cpp',
        '$libcalcManagerPath/CEngine/scidisp.cpp',
        '$libcalcManagerPath/CEngine/scifunc.cpp',
        '$libcalcManagerPath/CEngine/scioper.cpp',
        '$libcalcManagerPath/CEngine/sciset.cpp',
        '$libcalcManagerPath/Ratpack/basex.cpp',
        '$libcalcManagerPath/Ratpack/conv.cpp',
        '$libcalcManagerPath/Ratpack/exp.cpp',
        '$libcalcManagerPath/Ratpack/fact.cpp',
        '$libcalcManagerPath/Ratpack/itrans.cpp',
        '$libcalcManagerPath/Ratpack/itransh.cpp',
        '$libcalcManagerPath/Ratpack/logic.cpp',
        '$libcalcManagerPath/Ratpack/num.cpp',
        '$libcalcManagerPath/Ratpack/rat.cpp',
        '$libcalcManagerPath/Ratpack/support.cpp',
        '$libcalcManagerPath/Ratpack/trans.cpp',
        '$libcalcManagerPath/Ratpack/transh.cpp',
        '$libcalcManagerPath/CalculatorHistory.cpp',
        '$libcalcManagerPath/CalculatorManager.cpp',
        '$libcalcManagerPath/ExpressionCommand.cpp',
        '$libcalcManagerPath/NumberFormattingUtils.cpp',
        '$libcalcManagerPath/pch.cpp',
        '$libcalcManagerPath/UnitConverter.cpp',
      ],
      language: Language.cpp,
      includes: [
        'src/include',
        libcalcManagerPath,
        '$libcalcManagerPath/Header Files',
        '$libcalcManagerPath/Ratpack',
      ],
      defines: defines,
      flags: flags,
      frameworks: frameworks,
      libraries: libraries,
      forcedIncludes: forcedIncludes,
    );

    await builder.run(
      input: input,
      output: output,
      logger: Logger('')
        ..level = Level.ALL
        ..onRecord.listen((record) => print(record.message)),
    );

    if (targetOs == OS.android) {
      // add libc++_shared.so from the NDK
      final aclang = await androidNdkClang.defaultResolver!.resolve(
        logger: Logger(''),
      );
      for (final tool in aclang) {
        if (tool.tool.name == 'Clang') {
          final sysroot = tool.uri.resolve('../sysroot/').toString();
          // use the arch from native_toolchain_c.
          String libPath =
              '${sysroot}usr/lib/${RunCBuilder.androidNdkClangTargetFlags[targetArchitecture]}/libc++_shared.so';
          // check if path exists.
          if (!File(libPath).existsSync()) {
            // if not we can try the alternate map (this will only fix ARM).
            libPath =
                '${sysroot}usr/lib/${androidNdkArchABIMap[targetArchitecture]}/libc++_shared.so';
          }
          output.assets.code.add(
            CodeAsset(
              package: input.packageName,
              name: 'libc++_shared.so',
              file: Uri.parse(libPath),
              linkMode: DynamicLoadingBundled(),
            ),
          );
          break;
        }
      }
    }
  });
}
